package org.gfix.parser;

import java.io.IOException;
import java.io.Reader;

import org.gfix.exception.FixException;
import org.gfix.parser.FixParseFault.Serverity;

/**
 * This class actually does the parsing of the message
 * @author alan
 *
 */
public class FixParser {

	/**
	 * The standard delimiter character for FIX messages.
	 */
	public static final int FIX_DELIMITER = '\001';
	public static final int FIX_CONTENT = '=';
	
	private FixMessageValidatorFactory validatorFactory;
	
	/**
	 * Creates a parser which is not validating.
	 */
	public FixParser()
	{
		this(new NonValidatingValidator());
	}
	
	public FixParser(FixMessageValidatorFactory validatorSource)
	{
		this.validatorFactory = validatorSource;
	}
	
	public void parse(MessageSource source, FixHandler handler) throws FixException
	{
		FixMessageValidator validator = validatorFactory.getFixMessageValidator();
		Reader r = source.getCharacterStream();
		// read until the the delimiter or to the End of stream
		// and check until the first '=' as well
		int read = -1;
		StringBuilder tagBuffer = new StringBuilder();
		StringBuilder contentBuffer = new StringBuilder();
		StringBuilder currentbuffer = tagBuffer;
		boolean buildingTag = true;
		try
		{
			do
			{			
				read = r.read();
				switch(read)
				{
					case -1:						
					case FIX_DELIMITER:
					{
						//check to see if there is an 'event to fire'
						if(tagBuffer.length() != 0)
						{
							processTagContent(tagBuffer,contentBuffer, handler,validator);
							//reset
							tagBuffer.setLength(0);
							contentBuffer.setLength(0);
							buildingTag = true;
							currentbuffer = tagBuffer;
						}
							
						break;
					}
					case FIX_CONTENT:
					{
						if(buildingTag)
						{
							currentbuffer = contentBuffer;
							buildingTag = false;
						}
						else
						{
							currentbuffer.append((char)read);	
						}
						break;
					}
					default:
					{
						currentbuffer.append((char)read);
						break;
					}
				}				
			}
			while(read != -1);
		
		} catch (IOException e)
		{
			throw new FixException("", e);
		}
	}
	
	private void processTagContent(StringBuilder tagBuffer,
			StringBuilder contentBuffer, 
			FixHandler handler,
			FixMessageValidator validator) throws FixException
	{
		try
		{
			int tag = Integer.parseInt(tagBuffer.toString().trim());
			String content = contentBuffer.toString();
			FixParseFault fault = validator.validate(tag, content);
			if(fault != null)
			{
				switch(fault.getServerity())
				{
					case ERROR:
						handler.error(fault);
						break;
					case WARNING:
						handler.warning(fault);
				}
			}
			handler.newTag(tag, content);
		}
		catch(NumberFormatException nfe)
		{
			handler.error(new FixParseFault(1,
					"Tag value :" + tagBuffer +": could not be parsed to a number",
					-1, contentBuffer.toString(),Serverity.ERROR));
		}
	}
	
}
